/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */
package cn.hermit.util;

/**
 * An enhanced assert tool.
 * 
 * 
 * @author Jack Gao (Chinese name : GAO JIANGUO, Email : linux.gjg@gmail.com)
 * @date 19 Jun, 2014
 */
public class AssertUtils {

    /**
     * Assertion for object is null.
     * 
     * @param obj
     * 		The target object
     */
    public static void assertNull(final Object obj) {
        if (null != obj) {
            throw new AssertionError();
        }
    }

    /**
     * Assertion for object is null.
     * 
     * @param object
     * 		The target object
     * @param name
     * 		The object name
     */
    public static <T> void assertNull(final T object, final String name) {
        if (null != object) {
            throw new AssertionError(name + " must be null.");
        }
    }

    /**
     * Assertion for object is not null.
     * 
     * @param obj
     * 		The target object
     */
    public static <T> T assertNotNull(final T obj) {
        if (null != obj) {
            return obj;
        } else {
            throw new AssertionError();
        }
    }

    /**
     * Assertion for object is not null.
     * 
     * @param object
     * 		The target object
     * @param name
     * 		The object name
     */
    public static <T> T assertNotNull(final T object, final String name) {
        if (null != object) {
            return object;
        } else {
            throw new AssertionError(name + " cannot be null.");
        }
    }

    /**
     * Assertion for condition is true.
     * 
     * @param condition
     * 		The target condition
     */
    public static void assertTrue(boolean condition) {
        assertTrue(null, condition);
    }

    /**
     * Assertion for condition is true.
     * 
     * @param message
     * 		If the condition is false, print fail with message
     * @param condition
     * 		The target condition
     */
    public static void assertTrue(String message, boolean condition) {
        if (!condition) {
            fail(message);
        }
    }

    /**
     * Assertion for condition is false.
     * 
     * @param condition
     * 		The target condition
     */
    public static void assertFalse(boolean condition) {
        assertFalse(null, !condition);
    }

    /**
     * Assertion for condition is false.
     * 
     * @param message
     * 		If the condition is true, print fail with message
     * @param condition
     * 		The target condition
     */
    public static void assertFalse(String message, boolean condition) {
        assertTrue(message, !condition);
    }

    /**
     * Assertion for the actual object and the expected object are equivalent.
     * 
     * @param message
     * 		Fail message of assertion
     * @param expected
     * 		The expected object
     * @param actual
     * 		The actual object
     */
    public static void assertEquals(String message, Object expected, Object actual) {
        if (expected == actual)
            return;
        if (expected == null || actual == null) {
            failNotEquals(message, expected, actual);
        }
        if (String.class.isInstance(expected) && String.class.isInstance(actual)) {
            assertEquals(message, expected.toString(), actual.toString(), true);
        } else if (!actual.equals(expected)) {
            failNotEquals(message, expected, actual);
        }
    }

    /**
     * Assertion for the actual object and the expected object are equivalent.
     * 
     * @param expected
     * 		The expected object
     * @param actual
     * 		The actual object
     */
    public static void assertEquals(Object expected, Object actual) {
        assertEquals(null, expected, actual);
    }

    /**
     * 
     *  Assertion for two objects are not equivalent.
     *  
     * @param message
     * 		Fail message of assertion
     * @param first
     * 		The first object
     * @param second
     * 		The second object
     */
    public static void assertNotEquals(String message, Object first, Object second) {
        if (first == second) {
            failEquals(message, first);
        }
        if (first == null)
            return;
        if (String.class.isInstance(first) && String.class.isInstance(second)) {
            assertNotEquals(message, first.toString(), second.toString(), true);
        } else if (first.equals(second)) {
            failEquals(message, first);
        }
    }

    /**
     * 
     *  Assertion for two objects are not equivalent.
     *  
     * @param first
     * 		The first object
     * @param second
     * 		The second object
     */
    public static void assertNotEquals(Object first, Object second) {
        assertNotEquals(null, first, second);
    }

    /**
     * Assertion for actual array and expected array are equivalent.
     * 
     * @param message
     * 		Fail message of assertion
     * @param expected
     * 		expected array
     * @param actual
     * 		actual array
     */
    public static void assertEquals(String message, Object[] expected, Object[] actual) {
        if (expected == actual)
            return;
        if (expected == null) {
            failNotEquals(message, expected, actual);
        }
        if (expected.length != actual.length) {
            failNotEquals(message, expected, actual);
        }
        for (int i = 0; i < expected.length; i++) {
            assertEquals(message, expected[i], actual[i]);
        }
    }

    /**
     * Assertion for actual array and expected array are equivalent.
     * 
     * @param expected
     * 		expected array
     * @param actual
     * 		actual array
     */
    public static void assertEquals(Object[] expected, Object[] actual) {
        assertEquals(null, expected, actual);
    }

    /**
     * 
     * Assertion for actual float number and actual float number are equivalent or not differ bigger than delta.
     * 
     * @param message
     * 		Fail message of assertion
     * @param expected
     * 		expected float number
     * @param actual
     * 		actual float number
     * @param delta
     * 		differ between the two float number
     */
    public static void assertEquals(String message, float expected, float actual, float delta) {
        if (Double.compare(expected, actual) == 0) {
            return;
        }
        if ((Math.abs(expected - actual) <= delta)) {
            return;
        }
        failNotEquals(message, expected, actual);
    }

    /**
     * 
     * Assertion for actual float number and actual float number are equivalent or not differ bigger than delta.
     * 
     * @param expected
     * 		expected float number
     * @param actual
     * 		actual float number
     * @param delta
     * 		differ between the two float number
     */
    public static void assertEquals(float expected, float actual, float delta) {
        if (Double.compare(expected, actual) == 0) {
            return;
        }
        if ((Math.abs(expected - actual) <= delta)) {
            return;
        }
        failNotEquals(null, expected, actual);
    }

    /**
     * 
     * Assertion for actual float number and actual float number are not equivalent and differ bigger than delta.
     * 
     * @param message
     * 		Fail message of assertion
     * @param expected
     * 		expected float number
     * @param actual
     * 		actual float number
     * @param delta
     * 		differ between the two float number
     */
    public static void assertNotEquals(String message, float expected, float actual, float delta) {
        if (Double.compare(expected, actual) == 0) {
            failEquals(message, expected);
        }
        if ((Math.abs(expected - actual) <= delta)) {
            failEquals(message, expected);
        }
    }

    /**
     * 
     * Assertion for actual float number and actual float number are not equivalent and differ bigger than delta.
     * 
     * @param expected
     * 		expected float number
     * @param actual
     * 		actual float number
     * @param delta
     * 		differ between the two float number
     */
    public static void assertNotEquals(float expected, float actual, float delta) {
        if (Double.compare(expected, actual) == 0) {
            failEquals(null, expected);
        }
        if ((Math.abs(expected - actual) <= delta)) {
            failEquals(null, expected);
        }
    }

    /**
     * 
     * Assertion for actual double number and actual double number are equivalent or not differ bigger than delta.
     * 
     * @param message
     * 		Fail message of assertion
     * @param expected
     * 		expected double number
     * @param actual
     * 		actual double number
     * @param delta
     * 		differ between the two double number
     */
    public static void assertEquals(String message, double expected, double actual, double delta) {
        if (Double.compare(expected, actual) == 0) {
            return;
        }
        if ((Math.abs(expected - actual) <= delta)) {
            return;
        }
        failNotEquals(message, expected, actual);
    }

    /**
     * 
     * Assertion for actual double number and actual double number are equivalent or not differ bigger than delta.
     * 
     * @param expected
     * 		expected double number
     * @param actual
     * 		actual double number
     * @param delta
     * 		differ between the two double number
     */
    public static void assertEquals(double expected, double actual, double delta) {
        if (Double.compare(expected, actual) == 0) {
            return;
        }
        if ((Math.abs(expected - actual) <= delta)) {
            return;
        }
        failNotEquals(null, expected, actual);
    }

    /**
     * 
     * Assertion for actual double number and actual double number are not equivalent and differ bigger than delta.
     * 
     * @param message
     * 		Fail message of assertion
     * @param expected
     * 		expected double number
     * @param actual
     * 		actual double number
     * @param delta
     * 		differ between the two double number
     */
    public static void assertNotEquals(String message, double expected, double actual, double delta) {
        if (Double.compare(expected, actual) == 0) {
            failEquals(message, expected);
        }
        if ((Math.abs(expected - actual) <= delta)) {
            failEquals(message, expected);
        }
    }

    /**
     * 
     * Assertion for actual double number and actual double number are not equivalent and differ bigger than delta.
     * 
     * @param expected
     * 		expected double number
     * @param actual
     * 		actual double number
     * @param delta
     * 		differ between the two double number
     */
    public static void assertNotEquals(double expected, double actual, double delta) {
        if (Double.compare(expected, actual) == 0) {
            failEquals(null, expected);
        }
        if ((Math.abs(expected - actual) <= delta)) {
            failEquals(null, expected);
        }
    }

    /**
     * Assertion for actual string and expected string are equivalent.
     * 
     * @param message
     * 		Fail message of assertion
     * @param expected
     * 		Expected string
     * @param actual
     * 		Actual string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break while comparing
     */
    public static void assertEquals(String message, String expected, String actual, boolean ignoreReturnKey) {
        assertEquals(message, expected, actual, ignoreReturnKey, false);
    }

    /**
     * Assertion for actual string and expected string are equivalent.
     * 
     * @param expected
     * 		Expected string
     * @param actual
     * 		Actual string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break while comparing
     */
    public static void assertEquals(String expected, String actual, boolean ignoreReturnKey) {
        assertEquals(null, expected, actual, ignoreReturnKey, false);
    }

    /**
     * Assertion for tow StringUtils are not equivalent.
     * 
     * @param message
     * 		Fail message of assertion
     * @param first
     * 		The first string
     * @param second
     * 		The second string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break while comparing
     */
    public static void assertNotEquals(String message, String first, String second, boolean ignoreReturnKey) {
        assertNotEquals(message, first, second, ignoreReturnKey, false);
    }

    /**
     * Assertion for tow StringUtils are not equivalent.
     * 
     * @param first
     * 		The first string
     * @param second
     * 		The second string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break while comparing
     */
    public static void assertNotEquals(String first, String second, boolean ignoreReturnKey) {
        assertNotEquals(null, first, second, ignoreReturnKey, false);
    }

    /**
     * Assertion for actual string and expected string are equivalent.
     * 
     * @param message
     * 		Fail message of assertion
     * @param expected
     * 		The expected string
     * @param actual
     * 		The actual string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     */
    public static void assertEquals(
            String message,
            String expected,
            String actual,
            boolean ignoreReturnKey,
            boolean ignoreCase) {
        assertEquals(message, expected, actual, ignoreReturnKey, ignoreCase, false);
    }

    /**
     * Assertion for actual string and expected string are equivalent.
     * 
     * @param expected
     * 		The expected string
     * @param actual
     * 		The actual string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     */
    public static void assertEquals(String expected, String actual, boolean ignoreReturnKey, boolean ignoreCase) {
        assertEquals(null, expected, actual, ignoreReturnKey, ignoreCase, false);
    }

    /**
     * Assertion for two StringUtils are not equivalent.
     * 
     * @param message
     * 		Fail message of assertion
     * @param first
     * 		The first string
     * @param second
     * 		The second string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     */
    public static void assertNotEquals(
            String message,
            String first,
            String second,
            boolean ignoreReturnKey,
            boolean ignoreCase) {
        assertNotEquals(message, first, second, ignoreReturnKey, ignoreCase, false);
    }

    /**
     * Assertion for two StringUtils are not equivalent.
     * 
     * @param first
     * 		The first string
     * @param second
     * 		The second string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     */
    public static void assertNotEquals(String first, String second, boolean ignoreReturnKey, boolean ignoreCase) {
        assertNotEquals(null, first, second, ignoreReturnKey, ignoreCase, false);
    }

    /**
     * Assertion for the actual string and the expected string are equivalent.
     * 
     * @param message
     * 		Fail message of assertion
     * @param expected
     * 		The expected string
     * @param actual
     * 		The actual string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     * @param needTrim
     * 		If true, will strip the string before comparison
     */
    public static void assertEquals(
            String message,
            String expected,
            String actual,
            boolean ignoreReturnKey,
            boolean ignoreCase,
            boolean needTrim) {
        assertEquals(message, expected, actual, ignoreReturnKey, ignoreCase, false, needTrim);
    }

    /**
     * Assertion for the actual string and the expected string are equivalent.
     * 
     * @param expected
     * 		The expected string
     * @param actual
     * 		The actual string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     * @param needTrim
     * 		If true, will strip the string before comparison
     */
    public static void assertEquals(
            String expected,
            String actual,
            boolean ignoreReturnKey,
            boolean ignoreCase,
            boolean needTrim) {
        assertEquals(null, expected, actual, ignoreReturnKey, ignoreCase, false, needTrim);
    }

    /**
     * Assertion for two StringUtils are not equivalent.
     * 
     * @param message
     * 		Fail message of assertion
     * @param first
     * 		The first string
     * @param second
     * 		The second string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     * @param needTrim
     * 		If true, will strip the string before comparison
     */
    public static void assertNotEquals(
            String message,
            String first,
            String second,
            boolean ignoreReturnKey,
            boolean ignoreCase,
            boolean needTrim) {
        assertNotEquals(message, first, second, ignoreReturnKey, ignoreCase, false, needTrim);
    }

    /**
     * Assertion for two StringUtils are not equivalent.
     * 
     * @param first
     * 		The first string
     * @param second
     * 		The second string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     * @param needTrim
     * 		If true, will strip the string before comparison
     */
    public static void assertNotEquals(
            String first,
            String second,
            boolean ignoreReturnKey,
            boolean ignoreCase,
            boolean needTrim) {
        assertNotEquals(null, first, second, ignoreReturnKey, ignoreCase, false, needTrim);
    }

    /**
     * Assertion for the actual string and the expected string are equivalent.
     * 
     * @param message
     * 		Fail message of assertion
     * @param expected
     * 		The expected string
     * @param actual
     * 		The actual string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     * @param ignoreNoDisplay
     * 		If true, will ignore non-display characters in comparison
     * @param needTrim
     * 		If true, will strip the string before comparison
     */
    public static void assertEquals(
            String message,
            String expected,
            String actual,
            boolean ignoreReturnKey,
            boolean ignoreCase,
            boolean ignoreNoDisplay,
            boolean needTrim) {
        int index = -1;
        if ((index = StringUtils.diffIndex(expected, actual, ignoreReturnKey, ignoreCase, ignoreNoDisplay, needTrim)) != -1) {
            failNotEquals(message, expected, actual, index);
        }
    }

    /**
     * Assertion for the actual string and the expected string are equivalent.
     * 
     * @param expected
     * 		The expected string
     * @param actual
     * 		The actual string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     * @param ignoreNoDisplay
     * 		If true, will ignore non-display characters in comparison
     * @param needTrim
     * 		If true, will strip the string before comparison
     */
    public static void assertEquals(
            String expected,
            String actual,
            boolean ignoreReturnKey,
            boolean ignoreCase,
            boolean ignoreNoDisplay,
            boolean needTrim) {
        assertEquals(null, expected, actual, ignoreReturnKey, ignoreCase, ignoreNoDisplay, needTrim);
    }

    /**
     * Assertion for two StringUtils are not equivalent.
     * 
     * @param message
     * 		Fail message of assertion
     * @param first
     * 		The first string
     * @param second
     * 		The second string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     * @param ignoreNoDisplay
     * 		If true, will ignore non-display characters in comparison
     * @param needTrim
     * 		If true, will strip the string before comparison
     */
    public static void assertNotEquals(
            String message,
            String first,
            String second,
            boolean ignoreReturnKey,
            boolean ignoreCase,
            boolean ignoreNoDisplay,
            boolean needTrim) {
        if (StringUtils.equals(first, second, ignoreReturnKey, ignoreCase, ignoreNoDisplay, needTrim)) {
            failEquals(message, first);
        }
    }

    /**
     * Assertion for two StringUtils are not equivalent.
     * 
     * @param first
     * 		The first string
     * @param second
     * 		The second string
     * @param ignoreReturnKey
     * 		If true, will ignore new line break in comparison
     * @param ignoreCase
     * 		If true, will be case insensitive in comparison
     * @param ignoreNoDisplay
     * 		If true, will ignore non-display characters in comparison
     * @param needTrim
     * 		If true, will strip the string before comparison
     */
    public static void assertNotEquals(
            String first,
            String second,
            boolean ignoreReturnKey,
            boolean ignoreCase,
            boolean ignoreNoDisplay,
            boolean needTrim) {
        assertNotEquals(null, first, second, ignoreReturnKey, ignoreCase, ignoreNoDisplay, needTrim);
    }

    /**
     * Throw AssertionError with formatted fail message.
     * 
     * @param message
     * 		Message need to format
     * @param actual
     * 		Actual object
     */
    private static void failEquals(String message, Object actual) {
        String formatted = "Values should be different. ";
        if (message != null) {
            formatted = message + ". ";
        }
        formatted += "Actual: " + actual;
        fail(formatted);
    }

    /**
     * Throw AssertionError with formatted fail message.
     * 
     * @param message
     * 		Message need to format
     * @param expected
     * 		Expected object
     * @param actual
     * 		Actual object
     */
    private static void failNotEquals(String message, Object expected, Object actual) {
        fail(format(message, expected, actual, -1));
    }

    private static void failNotEquals(String message, Object expected, Object actual, int index) {
        fail(format(message, expected, actual, index));
    }

    /**
     * Format assertion message with serialization of the actual object and the expected objec.
     * @param message
     * 		Assertion message.
     * @param expected
     * 		Expected object
     * @param actual
     * 		Actual object
     * @param index
     * 		Index of differences occurred in between expected object and actual object.
     * 
     * @return
     * 		Formatted string
     */
    private static String format(String message, Object expected, Object actual, int index) {
        StringBuilder formattedBuilder = new StringBuilder();
        if (message != null && !message.equals("")) {
            formattedBuilder.append(message).append(" ");
        }
        String expectedString = StringUtils.valueOf(expected);
        String actualString = StringUtils.valueOf(actual);
        if (String.class.isInstance(expected) || String.class.isInstance(actual)) {
            if (index != -1) {
                if (index < expectedString.length() && index < actualString.length()) {
                    expectedString = expectedString.substring(0, index + 1);
                    actualString = actualString.substring(0, index + 1);
                    formattedBuilder.append("expected:\n<").append(expectedString).append("> \nbut was:\n<").append(
                        actualString).append('>');
                    return formattedBuilder.toString();
                } else if (index < expectedString.length()) {
                    formattedBuilder.append("expected:\n<").append(expectedString).append("> \nbut was:\n<").append(
                        actualString).append('>').append("\nexpected string has more characters:\n<").append(
                        expectedString.substring(index)).append('>');
                    return formattedBuilder.toString();
                } else {
                    formattedBuilder.append("expected:\n<").append(expectedString).append("> \nbut was:\n<").append(
                        actualString).append('>').append("\nactual string has more characters:\n<").append(
                        actualString.substring(index)).append('>');
                    return formattedBuilder.toString();
                }
            } else {
                formattedBuilder.append("expected:\n<").append(expectedString).append("> \nbut was:\n<").append(
                    actualString).append('>');
                return formattedBuilder.toString();
            }
        } else {
            formattedBuilder.append("expected:\n<").append(formatClassAndValue(expected, expectedString)).append(
                "> \nbut was:\n<").append(formatClassAndValue(actual, actualString)).append('>');
            return formattedBuilder.toString();
        }
    }

    /**
     * Format assertion message for non-string object.
     * 
     * @param value
     * 		non-string object
     * @param valueString
     * 		assertion message
     * @return
     */
    private static String formatClassAndValue(Object value, String valueString) {
        String className = value == null ? "null" : value.getClass().getName();
        return className + "<" + valueString + ">";
    }

    /**
     * Fails a test without any message.
     * 
     */
    public static void fail() {
        fail(null);
    }

    /**
     * Fails a test with the given message.
     * 
     * @param message
     *            the identifying message for the {@link AssertionError} ( <code>null</code> okay)
     * @see AssertionError
     */
    public static void fail(String message) {
        if (message == null) {
            throw new AssertionError();
        }
        throw new AssertionError(message);
    }

}